package com.rybbaby.tss.core.verifier;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;

/**
 * @description 信用卡卡号有效性验证器
 * 
 * @author 谢进伟
 * 
 * @createDate 2016年3月30日 下午2:20:52
 */
public class CreditCardVerifier extends AbstractVerifier {
	
	/**
	 * 是否允许为空值
	 */
	private boolean nullable = true;
	
	private static Map<String , Map<String , Object []>> cards = new HashMap<String , Map<String , Object []>>();
	
	static {
		// 验证基于前缀的卡号和长度
		addcads(cards , "AMERICAN_EXPRESS" ,//
				new Integer []{15} ,//
				new String []{"34" , "37"});
		addcads(cards , "DINERS_CLUB" ,//
				new Integer []{14} ,//
				new String []{"300" , "301" , "302" , "303" , "304" , "305" , "36"});
		addcads(cards , "DINERS_CLUB_US" ,//
				new Integer []{16} ,//
				new String []{"54" , "55"});
		addcads(cards , "DISCOVER" ,//
				new Integer []{16} ,//
				new String []{"6011" , "622126" , "622127" , "622128" , "622129" , "62213" , "62214" , "62215" , "62216" , "62217" , "62218" , "62219" , "6222" , "6223" , "6224" , "6225" , "6226" , "6227" , "6228" , "62290" , "62291" , "622920" , "622921" , "622922" , "622923" , "622924" , "622925" , "644" , "645" , "646" , "647" , "648" , "649" , "65"});
		addcads(cards , "JCB" ,//
				new Integer []{16} ,//
				new String []{"3528" , "3529" , "353" , "354" , "355" , "356" , "357" , "358"});
		addcads(cards , "LASER" ,//
				new Integer []{16 , 17 , 18 , 19} ,//
				new String []{"6304" , "6706" , "6771" , "6709"});
		addcads(cards , "MAESTRO" ,//
				new Integer []{12 , 13 , 14 , 15 , 16 , 17 , 18 , 19} ,//
				new String []{"5018" , "5020" , "5038" , "6304" , "6759" , "6761" , "6762" , "6763" , "6764" , "6765" , "6766"});
		addcads(cards , "MASTERCARD" ,//
				new Integer []{16} ,//
				new String []{"51" , "52" , "53" , "54" , "55"});
		addcads(cards , "SOLO" ,//
				new Integer []{16 , 18 , 19} ,//
				new String []{"6334" , "6767"});
		addcads(cards , "UNIONPAY" ,//
				new Integer []{16 , 17 , 18 , 19} ,//
				new String []{"622126" , "622127" , "622128" , "622129" , "62213" , "62214" , "62215" , "62216" , "62217" , "62218" , "62219" , "6222" , "6223" , "6224" , "6225" , "6226" , "6227" , "6228" , "6253" , "62290" , "62291" , "622920" , "622921" , "622922" , "622923" , "622924" , "622925"});
		addcads(cards , "VISA" ,//
				new Integer []{16} ,//
				new String []{"4"});
	}
	
	/**
	 * 获取 是否允许为空值
	 * 
	 * @return 是否允许为空值
	 */
	public boolean isNullable() {
		return this.nullable;
	}
	
	/**
	 * 设置 是否允许为空值
	 * 
	 * @param nullable
	 *            是否允许为空值
	 */
	public void setNullable(boolean nullable) {
		this.nullable = nullable;
	}
	
	/**
	 * 验证信用卡卡号是否有效
	 * 
	 * @param params
	 *            信用卡卡号
	 */
	@Override
	public boolean validate(Object params) {
		return validate(params , nullable , null , new ValidationRule() {
			
			@Override
			public boolean check(Object params) {
				return checkCard(params);
			}
		});
	}
	
	private boolean checkCard(Object object) {
		String value = object.toString();
		// 接受数字、破折号或空格
		if(value.matches("[^0-9-\\s]+")) {
			return false;
		}
		value = value.replaceAll("\\D" , "");
		return luhn(value);// && partCheck(value);
	}
	
	/**
	 * 部分银行信用卡验证
	 * 
	 * @param value
	 * @return
	 */
	public static boolean partCheck(String value) {
		if(!luhn(value)) {
			return false;
		}
		int valueLength = value.length();
		Iterator<String> iterator = cards.keySet().iterator();
		while (iterator.hasNext()) {
			String typeKey = iterator.next();
			Map<String , Object []> card = cards.get(typeKey);
			Object [] lengths = card.get("length");
			for(Object prefix : card.get("prefix")) {
				if(value.substring(0 , prefix.toString().length()).equals(prefix) && ArrayUtils.indexOf(lengths , valueLength) > -1) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 添加信用卡
	 * 
	 * @param map
	 *            信用卡容器
	 * @param key
	 *            行用卡类型
	 * @param length
	 *            有效长度
	 * @param prefix
	 *            有效前缀
	 */
	private static void addcads(Map<String , Map<String , Object []>> map , String key , Integer [] length , String [] prefix) {
		Map<String , Object []> AMERICAN_EXPRESS = new HashMap<String , Object []>();
		AMERICAN_EXPRESS.put("length" , length);
		AMERICAN_EXPRESS.put("prefix" , prefix);
		map.put(key , AMERICAN_EXPRESS);
	}
	
	/**
	 * Luhn验证算法
	 * 
	 * @param value
	 * @return
	 */
	private static boolean luhn(String value) {
		int length = value.length() , mul = 0 , sum = 0;
		int prodArr [][] = {{0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9} , {0 , 2 , 4 , 6 , 8 , 1 , 3 , 5 , 7 , 9}};
		while (length != 0) {
			length--;
			Integer ch = Integer.valueOf(value.charAt(length) + "");
			sum += prodArr[mul][ch];
			mul ^= 1;
		}
		return(sum % 10 == 0 && sum > 0);
	}
}
